import math
from typing import Generator, List, NoReturn, Tuple, Optional

import wx
from PIL import Image

from event_bus import EventBus
from models import Sample
from uis import SampleView
from view_models import project_state
from wxutils import call_after
from .preview_panel import PreviewPanel


class SamplePreview(SampleView):
    _current_page: int = 1
    _total_pages: int = 1
    _samples_cache: Generator[Tuple[int, int, List[str]], int, NoReturn]
    current_sample: str

    def __init__(self, parent=None):
        super().__init__(parent)

        preview_container = self.mark_list.GetContainingSizer()
        self.previewer = PreviewPanel(self)
        preview_container.Add(self.previewer, wx.GBPosition(0, 1), wx.GBSpan(1, 1), wx.ALL | wx.EXPAND, 5)
        preview_container.AddGrowableCol(1)

        self.Bind(wx.EVT_LISTBOX, self.on_select_sample, self.sample_list)
        self.Bind(wx.EVT_CHECKLISTBOX, self.on_change_category, self.mark_list)
        self.Bind(wx.EVT_BUTTON, self.prev_page, self.prev_slice)
        self.Bind(wx.EVT_BUTTON, self.next_page, self.next_slice)
        self.Bind(wx.EVT_BUTTON, self.first_page, self.top_slice)
        self.Bind(wx.EVT_BUTTON, self.last_page, self.tail_slice)
        self.Bind(wx.EVT_BUTTON, self.jump_page, self.page_jump)

        EventBus().register_topic(topic="switched_to_preview", handler=self.load_samples)
        EventBus().register_topic(topic="categories_updated", handler=self.update_categories)
        EventBus().register_topic(topic="project_changed", handler=self.when_project_changed)

    @call_after
    def when_project_changed(self, _):
        self.mark_list.SetCheckedStrings([])

    @staticmethod
    def initialize_samples(size: int = 100) -> Generator[Tuple[int, int, List[str]], int, NoReturn]:
        request_page = 1
        total_samples = Sample.select().count()
        total_pages = math.ceil(total_samples / size)
        while True:
            selected_samples = [s.target_fingerprint
                                for s in Sample.select().order_by(Sample.serial).paginate(request_page, size)]
            request_page = yield total_pages, request_page, selected_samples
            request_page = min([max([1, request_page]), total_pages])

    def update_categories(self, event=None, checked: List[str] = None):
        categories = {item for item in project_state.categories}
        if event is not None and "change_project" in event and event["change_project"]:
            previous_checked = []
        else:
            previous_checked = set(self.mark_list.GetCheckedStrings()) & categories
        self.mark_list.SetItems(sorted(categories))
        if checked is None:
            self.mark_list.SetCheckedStrings(list(previous_checked))
        else:
            self.mark_list.SetCheckedStrings(checked)

    def load_samples(self, _):
        self._samples_cache = self.initialize_samples()
        self.load_paged_samples()

    def load_paged_samples(self, page: Optional[int] = None):
        self._total_pages, self._current_page, sample_slice = self._samples_cache.send(page)
        self.slice_summary.SetLabel(f"共{self._total_pages}页")
        self.slice_page.SetMax(int(self._total_pages))
        self.slice_page.SetValue(int(self._current_page))
        self.sample_list.SetItems(sample_slice)
        self.update_categories(checked=[])
        self.previewer.clear_image()
        if hasattr(self, "current_sample"):
            del self.current_sample

    def on_select_sample(self, event: wx.CommandEvent):
        self.current_sample = event.GetString()
        sample = Sample.get(Sample.target_fingerprint == self.current_sample)
        self.update_categories(checked=sample.categories)
        source_image = Image.open(sample.source)
        cropped_image = source_image.crop((max([0, sample.crop_x]),
                                           max([0, sample.crop_y]),
                                           min([sample.crop_x + sample.crop_width, source_image.width]),
                                           min([sample.crop_y + sample.crop_height, source_image.height])))
        new_image = Image.new(mode="RGBA", size=(sample.crop_width, sample.crop_height))
        dest_x = abs(sample.crop_x) if sample.crop_x < 0 else 0
        dest_y = abs(sample.crop_y) if sample.crop_y < 0 else 0
        new_image.alpha_composite(cropped_image.convert(mode="RGBA"), (dest_x, dest_y))
        self.previewer.load_pil_image(new_image)

    def on_change_category(self, _):
        if hasattr(self, "current_sample"):
            Sample.update(categories=self.mark_list.GetCheckedStrings()) \
                .where(Sample.target_fingerprint == self.current_sample) \
                .execute()
            EventBus().post_topic(topic="sample_updated", event=None)

    def prev_page(self, _):
        self.load_paged_samples(self._current_page - 1)

    def next_page(self, _):
        self.load_paged_samples(self._current_page + 1)

    def first_page(self, _):
        self.load_paged_samples(1)

    def last_page(self, _):
        self.load_paged_samples(self._total_pages)

    def jump_page(self, _):
        self.load_paged_samples(int(self.slice_page.GetValue()))
